package lxs.swift.collector.wrap;

import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

public interface Either<L, R> extends Serializable {

    boolean isLeft();

    boolean isRight();

    default <E extends Throwable> L expectLeft(String expect, @NotNull Function<String, E> func) throws E {
        if (this.isLeft()) {
            Left<L, R> left = (Left<L, R>) this;
            return left.value;
        }

        throw func.apply(expect);
    }

    default <E extends Throwable> R expectRight(String expect, @NotNull Function<String, E> func) throws E {
        if (this.isLeft()) {
            throw func.apply(expect);
        }
        Right<L, R> right = (Right<L, R>) this;
        return right.value;
    }

    default L expectLeft(String expect) {
        return this.expectLeft(expect, RuntimeException::new);
    }

    default R expectRight(String expect) {
        return this.expectRight(expect, RuntimeException::new);
    }

    default L unwrapLeft() {
        return this.expectLeft("called `Either::unwrapLeft()` on a `Right` value");
    }

    default R unwrapRight() {
        return this.expectRight("called `Either::unwrapRight()` on a `Left` value");
    }

    default <T> Either<T, R> mapLeft(@NotNull Function<L, T> func) {
        Objects.requireNonNull(func);
        if (this.isLeft()) {
            L l = this.unwrapLeft();
            T t = func.apply(l);
            return Either.left(t);
        }
        R r = this.unwrapRight();
        return Either.right(r);
    }

    default <T> Either<L, T> mapRight(@NotNull Function<R, T> func) {
        Objects.requireNonNull(func);
        if (this.isLeft()) {
            L l = this.unwrapLeft();
            return Either.left(l);
        }
        R r = this.unwrapRight();
        T t = func.apply(r);
        return Either.right(t);
    }

    default Either<L, R> copy() {
        if (this.isLeft()) {
            Left<L, R> left = (Left<L, R>) this;
            return Either.left(left.value);
        }
        Right<L, R> right = (Right<L, R>) this;
        return Either.right(right.value);
    }

    default Either<R, L> swap() {
        if (this.isLeft()) {
            Left<L, R> left = (Left<L, R>) this;
            return Either.right(left.value);
        }
        Right<L, R> right = (Right<L, R>) this;
        return Either.left(right.value);
    }


    default <RR> RR matchMap(Function<L, RR> left, Function<R, RR> right) {
        if (this.isLeft()) {
            return left.apply(unwrapLeft());
        }
        return right.apply(unwrapRight());
    }

    default void matchRun(Consumer<L> left, Consumer<R> right) {
        if (this.isLeft()) {
            left.accept(unwrapLeft());
            return;
        }
        right.accept(unwrapRight());
    }

    default <RR> RR selected(RR left, RR right) {
        if (this.isLeft()) {
            return left;
        }
        return right;
    }

    static <L, R> Either<L, R> of(L left, R right) {
        if (left != null) {
            return Either.left(left);
        }
        if (right != null) {
            return Either.right(right);
        }
        throw new IllegalStateException("Either is null");
    }

    static <L, R> Either<L, R> left(@NotNull L left) {
        Objects.requireNonNull(left);
        return new Left<>(left);
    }

    static <L, R> Either<L, R> right(@NotNull R right) {
        Objects.requireNonNull(right);
        return new Right<>(right);
    }

    class Left<L, R> implements Either<L, R> {
        public final L value;

        private Left(L value) {
            this.value = value;
        }

        @Override
        public boolean isLeft() {
            return true;
        }

        @Override
        public boolean isRight() {
            return false;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Left<?, ?> left = (Left<?, ?>) o;
            return Objects.equals(value, left.value);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        @Override
        public String toString() {
            String typeName = value.getClass().getTypeName();
            String valueStr = value.toString();
            if (value instanceof CharSequence) {
                valueStr = "\"" + valueStr + "\"";
            }
            return typeName + ":Either.Left(" + valueStr + ")";
        }
    }

    class Right<L, R> implements Either<L, R> {
        public final R value;

        private Right(R value) {
            this.value = value;
        }

        @Override
        public boolean isLeft() {
            return false;
        }

        @Override
        public boolean isRight() {
            return true;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Right<?, ?> right = (Right<?, ?>) o;
            return Objects.equals(value, right.value);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        @Override
        public String toString() {
            String typeName = value.getClass().getTypeName();
            String valueStr = value.toString();
            if (value instanceof CharSequence) {
                valueStr = "\"" + valueStr + "\"";
            }
            return typeName + ":Either.Right(" + valueStr + ")";
        }
    }


}
